﻿using System;
using System.Net;
using System.Threading;

namespace Brjnk.Maps.Downloader
{
    public class DownloadThread
    {
        private DownloadScheduler scheduler;
        private IDownloadLogger logger;
        private Thread thread;

        public DownloadThread(DownloadScheduler scheduler, IDownloadLogger logger)
        {
            this.scheduler = scheduler;
            this.logger = logger;
            thread = new Thread(ThreadFunction);
            thread.IsBackground = true;
            thread.Start();
        }

        /// <summary>
        /// Znovuobnoví činnost vlákna (např. po nečinosti z důvodu žádného stahování)
        /// </summary>
        public void Continue()
        {
            are.Set();
        }

        private WebClient wc;

        private AutoResetEvent are = new AutoResetEvent(false);

        private void ThreadFunction()
        {
            wc = new WebClient();
            while (true)
            {
                are.WaitOne();
                while (true)
                {
                    try
                    {
                        var item = scheduler.GetNextTask(this);
                        if ((object)item == null) break;
                        Download(item);
                    }
                    catch (Exception ex)
                    {
                        ExceptionHandler(ex);
                    }
                }
            }
        }

        private void ExceptionHandler(Exception ex)
        {
            System.Diagnostics.Debug.Fail("An error occured during downloading data.", ex.ToString());
        }

        private void Download(DownloadTaskWithUpdater item)
        {
            System.IO.Stream input = null;
            System.IO.MemoryStream output = null;
            try
            {
                item.Updater.updateState(DownloadTaskState.Initializing);
                AddHttpHeaders();
                input = wc.OpenRead(item.Task.Uri);
                output = new System.IO.MemoryStream();
                int total;
                int.TryParse(wc.ResponseHeaders["Content-Length"], out total);
                byte[] buffer = new byte[4096];
                item.Updater.updateTotalBytes(total);
                item.Updater.updateState(DownloadTaskState.DownloadInProgress);
                var lastTime = DefaultTimingProvider.GetDefaultTimingProvider().GetTime();
                while (true)
                {
                    var len = input.Read(buffer, 0, buffer.Length);
                    if (len == 0)
                    {
                        CompleteDownload(item, output);
                        break;
                    }
                    else
                    {
                        var currentTime = DefaultTimingProvider.GetDefaultTimingProvider().GetTime();
                        UpdateProgress(item, len, currentTime - lastTime);
                        output.Write(buffer, 0, len);
                        lastTime = currentTime;
                    }
                }
            }
            catch (Exception ex)
            {
                CompleteDownload(item, ex);
            }
            finally
            {
                if (input != null) input.Dispose();
                if (output != null) output.Dispose();
            }
        }

        private void CompleteDownload(DownloadTaskWithUpdater task, Exception ex)
        {
            task.Updater.updateData(new byte[] { });
            task.Updater.updateException(ex);
            task.Updater.updateState(DownloadTaskState.Failed);
            task.Updater.raiseCompleted();
            if (DownloadCompleted != null)
                DownloadCompleted(this, new DownloadCompletedEventArgs(task.Task));

            if (logger != null) logger.LogDownloadCompleted(task.Task.Uri.Host, false);
            scheduler.IncrementOverloadProtection(this, (ulong)task.Task.ReceivedBytes);
        }

        private void CompleteDownload(DownloadTaskWithUpdater task, System.IO.MemoryStream output)
        {
            task.Updater.updateData(output.ToArray());
            if (output.Length < task.Task.TotalBytes)
                task.Updater.updateState(DownloadTaskState.FileIsTooShort);
            else
                task.Updater.updateState(DownloadTaskState.Success);

            if (DownloadCompleted != null)
                DownloadCompleted(this, new DownloadCompletedEventArgs(task.Task));
            task.Updater.raiseCompleted();

            if (logger != null) logger.LogDownloadCompleted(task.Task.Uri.Host, true);
            scheduler.IncrementOverloadProtection(this, (ulong)task.Task.ReceivedBytes);
        }

        private void UpdateProgress(DownloadTaskWithUpdater task, int length, TimeSpan timeSpan)
        {
            task.Updater.updateReceivedBytes(task.Task.ReceivedBytes + length);
            if (logger != null) logger.LogDownload(task.Task.Uri, length, timeSpan);
        }

        private void AddHttpHeaders()
        {
            wc.Headers.Add("User-Agent", "GPX Explorer");
            //wc.Headers.Add("User-Agent", @"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Maxthon/4.1.0.3000 Chrome/26.0.1410.43 Safari/537.1");
        }

        public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;

        public void Abort()
        {
            thread.Abort();
        }
    }

    public class DownloadCompletedEventArgs : EventArgs
    {
        /// <summary>
        /// Třída je thread-safe, pokud obdrží v konstruktoru DownloadScheduler, který je thread-safe
        /// </summary>
        public DownloadCompletedEventArgs(DownloadTask task)
        {
            this.Task = task;
        }

        public DownloadTask Task { get; protected set; }
    }
}